﻿using Microsoft.Web.WebView2.Core;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WebCapture
{
    public partial class MainForm3 : Form
    {


        private const string DefaultSaveFolder = @"C:\Temp";
        private const int MAX_SINGLE_CAPTURE_DIM = 8192;

        private CancellationTokenSource captureCts;
        private string saveFolder = DefaultSaveFolder;
        private string logFilePath => Path.Combine(saveFolder, "web_capture_log.txt");



        public MainForm3()
        {
            InitializeComponent();

            txtUrl.KeyDown += TxtUrl_KeyDown;
        }

        private async void MainForm3_Load(object sender, EventArgs e)
        {  

            try
            {
                AppendLog("初始化 WebView2...");
                await webView.EnsureCoreWebView2Async();
                AppendLog("WebView2 初始化完成。");

                // ✅ 拦截新窗口，实现强制在同一 WebView 内跳转
                webView.CoreWebView2.NewWindowRequested += (s, evt) =>
                {
                    evt.Handled = true;
                    webView.CoreWebView2.Navigate(evt.Uri);
                };

            }
            catch (Exception ex)
            {
                AppendLog("WebView2 初始化失败: " + ex.Message);
                MessageBox.Show("WebView2 初始化失败: " + ex.Message);
            }
        }


        #region 日志与进度

        private void AppendLog(string text)
        {
            if (txtLog.InvokeRequired)
            {
                txtLog.Invoke(new Action(() => AppendLog(text)));
                return;
            }

            string line = $"{DateTime.Now:HH:mm:ss} - {text}";
            txtLog.AppendText(line + Environment.NewLine);

            // 写入日志文件
            try
            {
                File.AppendAllText(logFilePath, line + Environment.NewLine);
            }
            catch { }

            txtLog.SelectionStart = txtLog.Text.Length;
            txtLog.ScrollToCaret();
        }

        private void SetProgress(int percent, string statusText = null)
        {
            if (progressBar.InvokeRequired)
            {
                progressBar.Invoke(new Action(() => SetProgress(percent, statusText)));
                return;
            }
            percent = Math.Max(0, Math.Min(100, percent));
            progressBar.Value = percent;
            lblPercent.Text = percent + "%";
            if (!string.IsNullOrEmpty(statusText))
                AppendLog(statusText);
        }

        private void ResetProgressAndLogs()
        {
            progressBar.Value = 0;
            lblPercent.Text = "0%";
            txtLog.Clear();
        }

        /// <summary>
        /// 如果没有 http/https 自动补全为 https://
        /// 如果没有 www 自动补全 www.
        /// </summary>
        private string NormalizeUrl(string input)
        {
            if (string.IsNullOrWhiteSpace(input))
                return string.Empty;

            input = input.Trim();

            // 如果没有 http/https 前缀，加上 https://
            if (!input.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
                !input.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
            {
                input = "https://" + input;
            }

            try
            {
                // 使用 Uri 来解析域名
                Uri uri = new Uri(input);

                string host = uri.Host;

                // 如果主机不以 www. 开头，自动加上
                if (!host.StartsWith("www.", StringComparison.OrdinalIgnoreCase))
                {
                    host = "www." + host;
                    UriBuilder builder = new UriBuilder(uri)
                    {
                        Host = host
                    };
                    input = builder.Uri.ToString();
                }
            }
            catch
            {
                // 如果解析失败，就保持原样
            }

            return input;
        }

        private void EnsureSaveFolder()
        {
            if (!Directory.Exists(saveFolder))
            {
                Directory.CreateDirectory(saveFolder);
                AppendLog($"自动创建保存文件夹: {saveFolder}");
            }
        }


        /// <summary>
        /// 在资源管理器中打开文件所在文件夹，并选中该文件
        /// </summary>
        private void OpenFolderAndSelectFile(string filePath)
        {
            try
            {
                if (!File.Exists(filePath))
                    return;

                // 检查文件夹是否已经打开
                string folder = Path.GetDirectoryName(filePath);
                bool folderAlreadyOpen = false;

                // Windows 资源管理器已经打开的文件夹无法直接判断，只能尝试用 explorer 打开
                // explorer.exe /select, 文件路径 可以在已打开资源管理器中选中文件
                System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{filePath}\"");
            }
            catch (Exception ex)
            {
                AppendLog("打开文件夹失败: " + ex.Message);
            }
        }
        
        #endregion


        private void TxtUrl_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                e.SuppressKeyPress = true; // ✅防止系统“叮”声
                btnReflash_Click(null, null); // ✅调用你已有的刷新方法
            }
        }


        private void btnChooseFolder_Click(object sender, EventArgs e)
        {
            using var fd = new FolderBrowserDialog();
            fd.SelectedPath = saveFolder;
            if (fd.ShowDialog() == DialogResult.OK)
            {
                saveFolder = fd.SelectedPath;
                AppendLog($"保存路径已修改为: {saveFolder}");
            }
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            if (captureCts != null)
            {
                captureCts.Cancel();
                AppendLog("停止请求已发送...");
            }
        }

        private async void btnReflash_Click(object sender, EventArgs e)
        {
            try
            {
                // 1️⃣ 检查 CoreWebView2 是否已初始化
                if (webView.CoreWebView2 == null)
                {
                    AppendLog("WebView2 未初始化，正在初始化...");
                    await webView.EnsureCoreWebView2Async();
                    AppendLog("WebView2 初始化完成。");
                }

                // 2️⃣ 获取 URL
                string url = NormalizeUrl(txtUrl.Text);
                txtUrl.Text = url;
                if (string.IsNullOrEmpty(url))
                {
                    MessageBox.Show("请先输入网址！");
                    return;
                }

                // 3️⃣ 重新导航（推荐用 Navigate，而不是 Reload）
                AppendLog($"正在重新加载页面: {url}");
                var navTcs = new TaskCompletionSource<bool>();

                void navHandler(object s, CoreWebView2NavigationCompletedEventArgs ev)
                {
                    navTcs.TrySetResult(true);
                    webView.NavigationCompleted -= navHandler;
                }

                webView.NavigationCompleted += navHandler;
                webView.CoreWebView2.Navigate(url); // ✅ 替代 Reload()
                await navTcs.Task;

                AppendLog("刷新完成。");
            }
            catch (Exception ex)
            {
                AppendLog($"刷新失败：{ex.Message}");
                MessageBox.Show("刷新出错：" + ex.Message);
            }
        }

        private async void btnCapture_Click(object sender, EventArgs e)
        {
            string url = txtUrl.Text.Trim();
            if (string.IsNullOrEmpty(url))
            {
                MessageBox.Show("请输入网址！");
                return;
            }

            btnCapture.Enabled = false;
            btnStop.Enabled = true;

            EnsureSaveFolder();

            if (webView.CoreWebView2 == null)
            {
                MessageBox.Show("WebView2 尚未初始化或网页未加载完成！");
                return;
            }

            btnCapture.Enabled = false;
            btnStop.Enabled = true;
            ResetProgressAndLogs();
            AppendLog($"开始截取整个网页...");

            captureCts = new CancellationTokenSource();
            var token = captureCts.Token;

            string finalPath = null;

            try
            {
                finalPath = await CaptureFullPageRobustAsync(saveFolder, token);
                AppendLog($"网页截图已保存: {finalPath}");

                // 弹出提示是否打开截图所在文件夹
                var result = MessageBox.Show(
                    "截图已完成，是否打开截图所在的文件夹？",
                    "完成",
                    MessageBoxButtons.YesNo,
                    MessageBoxIcon.Question
                );

                if (result == DialogResult.Yes && !string.IsNullOrEmpty(finalPath))
                {
                    OpenFolderAndSelectFile(finalPath);
                }
            }
            catch (Exception ex)
            {
                AppendLog("截图失败: " + ex.Message);
                MessageBox.Show("截图失败: " + ex.Message);
            }
            finally
            {
                btnCapture.Enabled = true;
                btnStop.Enabled = false;
                SetProgress(100, "完成");
            }
        }


        #region 截图核心逻辑（支持停止和保存未完成截图）

        private async Task<string> CaptureFullPageRobustAsync1(string saveFolder, CancellationToken token)
        {
            var core = webView.CoreWebView2 ?? throw new InvalidOperationException("WebView2 未初始化。");

            AppendLog("启用 Page 模块...");
            await core.CallDevToolsProtocolMethodAsync("Page.enable", "{}");

            // 注入强制展开 JS
            var helperJs = @"
                (function(){
                  try {document.documentElement.style.overflow='visible';document.body.style.overflow='visible';}catch(e){}
                  try {document.documentElement.style.height='auto';document.body.style.height='auto';}catch(e){}
                  try{var els=document.querySelectorAll('*');for(var i=0;i<els.length;i++){var s=getComputedStyle(els[i]);if(s&&(s.overflow==='hidden'||s.overflowY==='hidden'||s.overflowX==='hidden')) els[i].style.overflow='visible';els[i].style.maxHeight='none';els[i].style.minHeight='0';}}catch(e){}
                  return true;
                })();";
            await core.ExecuteScriptAsync(helperJs);
            AppendLog("网页已展开。");
            await Task.Delay(300);

            // 获取网页尺寸
            var raw = await core.ExecuteScriptAsync(
                "JSON.stringify({ width: Math.max(document.documentElement.scrollWidth||0, document.body.scrollWidth||0)," +
                "height: Math.max(document.documentElement.scrollHeight||0, document.body.scrollHeight||0)," +
                "viewportWidth: window.innerWidth, viewportHeight: window.innerHeight, dpr: window.devicePixelRatio||1 })");
            var json = UnwrapExecuteScriptResult(raw);
            using var doc = JsonDocument.Parse(json);
            int contentWidth = doc.RootElement.GetProperty("width").GetInt32();
            int contentHeight = doc.RootElement.GetProperty("height").GetInt32();
            int viewportWidth = doc.RootElement.GetProperty("viewportWidth").GetInt32();
            int viewportHeight = doc.RootElement.GetProperty("viewportHeight").GetInt32();
            double dpr = doc.RootElement.GetProperty("dpr").GetDouble();

            AppendLog($"网页内容尺寸: {contentWidth}x{contentHeight}, 视口: {viewportWidth}x{viewportHeight}, DPR={dpr}");

            contentWidth = Math.Max(1, contentWidth);
            contentHeight = Math.Max(1, contentHeight);

            // 判断单次截图可行性
            if (contentWidth <= MAX_SINGLE_CAPTURE_DIM && contentHeight <= MAX_SINGLE_CAPTURE_DIM)
            {
                try
                {
                    AppendLog("尝试单次截图...");
                    SetProgress(5, "单次截图");

                    var metrics = new { width = contentWidth, height = contentHeight, deviceScaleFactor = dpr, mobile = false, scale = 1 };
                    await core.CallDevToolsProtocolMethodAsync("Emulation.setDeviceMetricsOverride", JsonSerializer.Serialize(metrics));

                    var captureParams = new { format = "png", fromSurface = true };
                    var captureJson = await core.CallDevToolsProtocolMethodAsync("Page.captureScreenshot", JsonSerializer.Serialize(captureParams));
                    using var jr = JsonDocument.Parse(captureJson);
                    var base64 = jr.RootElement.GetProperty("data").GetString();
                    if (string.IsNullOrEmpty(base64)) throw new Exception("返回截图为空。");

                    var bytes = Convert.FromBase64String(base64);
                    string filename = $"网页截图_{DateTime.Now:yyyyMMdd_HHmmss}.png";
                    string path = Path.Combine(saveFolder, filename);
                    await File.WriteAllBytesAsync(path, bytes);

                    AppendLog("单次截图成功。");
                    await core.CallDevToolsProtocolMethodAsync("Emulation.clearDeviceMetricsOverride", "{}");
                    SetProgress(100, "已保存 (单次截图)");
                    return path;
                }
                catch { await core.CallDevToolsProtocolMethodAsync("Emulation.clearDeviceMetricsOverride", "{}"); AppendLog("单次截图失败，使用滚动拼接。"); }
            }
            else
            {
                AppendLog("内容过大，使用滚动拼接截图。");
            }

            return await CaptureByScrollAsync(core, contentWidth, contentHeight, viewportWidth, viewportHeight, dpr, saveFolder, token);
        }

        private async Task<string> CaptureByScrollAsync(CoreWebView2 core, int contentWidth, int contentHeight,
            int viewportWidth, int viewportHeight, double dpr, string saveFolder, CancellationToken token)
        {
            int vertSeg = (int)Math.Ceiling((double)contentHeight / viewportHeight);
            AppendLog($"滚动拼接: {vertSeg} 段");
            SetProgress(1, "开始滚动拼接");

            var images = new List<Image>();
            try
            {
                for (int i = 0; i < vertSeg; i++)
                {
                    token.ThrowIfCancellationRequested();

                    int scrollY = i * viewportHeight;
                    int segHeightCss = Math.Min(viewportHeight, contentHeight - scrollY);

                    AppendLog($"滚动到 y={scrollY} (段 {i + 1}/{vertSeg})");
                    SetProgress((int)((i / (double)vertSeg) * 70) + 1, $"滚动 {i + 1}/{vertSeg}");

                    var jsScroll = $@"
                    (async function(){{
                      window.scrollTo(0, Math.max(0, {scrollY}-50)); 
                      await new Promise(r=>setTimeout(r,120));
                      window.scrollTo(0,{scrollY});
                      await new Promise(r=>setTimeout(r,350));
                      return true;
                    }})();";
                    await core.ExecuteScriptAsync(jsScroll);

                    var captureJson = await core.CallDevToolsProtocolMethodAsync("Page.captureScreenshot", "{}");
                    using var jr = JsonDocument.Parse(captureJson);
                    var base64 = jr.RootElement.GetProperty("data").GetString();
                    if (string.IsNullOrEmpty(base64)) throw new Exception($"段 {i + 1} 截图为空");

                    var bytes = Convert.FromBase64String(base64);
                    using var ms = new MemoryStream(bytes);
                    var img = Image.FromStream(ms);
                    images.Add(new Bitmap(img));

                    await Task.Delay(120);
                }

                token.ThrowIfCancellationRequested();

                AppendLog("开始拼接图片...");
                SetProgress(75, "拼接中");

                int finalWidthPx = (int)Math.Round(contentWidth * dpr);
                int finalHeightPx = (int)Math.Round(contentHeight * dpr);

                var finalBitmap = new Bitmap(finalWidthPx, finalHeightPx);
                using (var g = Graphics.FromImage(finalBitmap))
                {
                    g.Clear(Color.White);
                    int currentYPx = 0;
                    for (int i = 0; i < images.Count; i++)
                    {
                        var seg = images[i];
                        g.DrawImage(seg, 0, currentYPx, seg.Width, seg.Height);
                        currentYPx += seg.Height;
                        seg.Dispose();

                        var p = 75 + (int)(((i + 1) / (double)images.Count) * 20);
                        SetProgress(p, $"拼接 {i + 1}/{images.Count}");
                    }
                }

                string filename = captureCts.IsCancellationRequested ?
                    $"网页截图_未完成_{DateTime.Now:yyyyMMdd_HHmmss}.png" :
                    $"网页截图_拼接_{DateTime.Now:yyyyMMdd_HHmmss}.png";

                string path = Path.Combine(saveFolder, filename);
                finalBitmap.Save(path, System.Drawing.Imaging.ImageFormat.Png);
                finalBitmap.Dispose();

                AppendLog("拼接完成，恢复滚动到顶部。");
                try { await core.ExecuteScriptAsync("window.scrollTo(0,0);"); } catch { }
                SetProgress(100, $"已保存 ({(captureCts.IsCancellationRequested ? "未完成" : "完成")})");
                return path;
            }
            catch (OperationCanceledException)
            {
                if (images.Count > 0)
                {
                    AppendLog("用户停止，保存已截取部分...");
                    string path = Path.Combine(saveFolder, $"网页截图_未完成_{DateTime.Now:yyyyMMdd_HHmmss}.png");
                    int finalWidthPx = (int)Math.Round(contentWidth * dpr);
                    int finalHeightPx = (int)Math.Round(contentHeight * dpr);

                    var finalBitmap = new Bitmap(finalWidthPx, finalHeightPx);
                    using (var g = Graphics.FromImage(finalBitmap))
                    {
                        g.Clear(Color.White);
                        int currentYPx = 0;
                        foreach (var seg in images)
                        {
                            g.DrawImage(seg, 0, currentYPx, seg.Width, seg.Height);
                            currentYPx += seg.Height;
                            seg.Dispose();
                        }
                    }
                    finalBitmap.Save(path, System.Drawing.Imaging.ImageFormat.Png);
                    finalBitmap.Dispose();
                    return path;
                }
                throw;
            }
        }

        private static string UnwrapExecuteScriptResult(string raw)
        {
            if (string.IsNullOrEmpty(raw)) return "{}";
            if (raw.Length >= 2 && raw[0] == '"' && raw[^1] == '"')
            {
                var inner = raw.Substring(1, raw.Length - 2);
                inner = inner.Replace("\\\"", "\"").Replace("\\\\", "\\");
                return inner;
            }
            return raw;
        }



        private async Task<string> CaptureFullPageRobustAsync(string saveFolder, CancellationToken token)
        {
            var core = webView.CoreWebView2 ?? throw new InvalidOperationException("WebView2 未初始化。");

            AppendLog("启用 Page 模块...");
            await core.CallDevToolsProtocolMethodAsync("Page.enable", "{}");

            // 1️⃣ 展开页面
            var helperJs = @"
        (function(){
            try {document.documentElement.style.overflow='visible';document.body.style.overflow='visible';}catch(e){}
            try {document.documentElement.style.height='auto';document.body.style.height='auto';}catch(e){}
            try{
                var els=document.querySelectorAll('*');
                for(var i=0;i<els.length;i++){
                    var s=getComputedStyle(els[i]);
                    if(s&&(s.overflow==='hidden'||s.overflowY==='hidden'||s.overflowX==='hidden'))
                        els[i].style.overflow='visible';
                    els[i].style.maxHeight='none';
                    els[i].style.minHeight='0';
                }
            }catch(e){}
            return true;
        })();";
            await core.ExecuteScriptAsync(helperJs);
            AppendLog("网页已展开。");

            // 2️⃣ 修正轮播图/轮播组件宽度，避免超宽
            var fixCarouselJs = @"
        (function(){
            try{
                var carousels = document.querySelectorAll('.carousel, .slider, .slick-slider');
                carousels.forEach(function(c){
                    c.style.width = '100%';
                    c.style.maxWidth = '100%';
                    c.style.overflow = 'hidden';
                });
            }catch(e){}
        })();";
            await core.ExecuteScriptAsync(fixCarouselJs);
            AppendLog("轮播图宽度已修正。");

            await Task.Delay(300); // 等待渲染完成

            // 3️⃣ 获取网页尺寸
            var raw = await core.ExecuteScriptAsync(
                "JSON.stringify({ width: Math.max(document.documentElement.scrollWidth||0, document.body.scrollWidth||0)," +
                "height: Math.max(document.documentElement.scrollHeight||0, document.body.scrollHeight||0)," +
                "viewportWidth: window.innerWidth, viewportHeight: window.innerHeight, dpr: window.devicePixelRatio||1 })");
            var json = UnwrapExecuteScriptResult(raw);
            using var doc = JsonDocument.Parse(json);
            int contentWidth = doc.RootElement.GetProperty("width").GetInt32();
            int contentHeight = doc.RootElement.GetProperty("height").GetInt32();
            int viewportWidth = doc.RootElement.GetProperty("viewportWidth").GetInt32();
            int viewportHeight = doc.RootElement.GetProperty("viewportHeight").GetInt32();
            double dpr = doc.RootElement.GetProperty("dpr").GetDouble();

            AppendLog($"网页内容尺寸: {contentWidth}x{contentHeight}, 视口: {viewportWidth}x{viewportHeight}, DPR={dpr}");

            // 4️⃣ 限制最大宽度，避免轮播图导致超宽
            int maxWidthPx = 1920;
            contentWidth = Math.Min(contentWidth, maxWidthPx);
            contentHeight = Math.Max(1, contentHeight);
            contentWidth = Math.Max(1, contentWidth);

            // 5️⃣ 判断单次截图是否可行
            if (contentWidth <= MAX_SINGLE_CAPTURE_DIM && contentHeight <= MAX_SINGLE_CAPTURE_DIM)
            {
                try
                {
                    AppendLog("尝试单次截图...");
                    SetProgress(5, "单次截图");

                    var metrics = new { width = contentWidth, height = contentHeight, deviceScaleFactor = dpr, mobile = false, scale = 1 };
                    await core.CallDevToolsProtocolMethodAsync("Emulation.setDeviceMetricsOverride", JsonSerializer.Serialize(metrics));

                    var captureParams = new { format = "png", fromSurface = true };
                    var captureJson = await core.CallDevToolsProtocolMethodAsync("Page.captureScreenshot", JsonSerializer.Serialize(captureParams));
                    using var jr = JsonDocument.Parse(captureJson);
                    var base64 = jr.RootElement.GetProperty("data").GetString();
                    if (string.IsNullOrEmpty(base64)) throw new Exception("返回截图为空。");

                    var bytes = Convert.FromBase64String(base64);
                    string filename = $"网页截图_{DateTime.Now:yyyyMMdd_HHmmss}.png";
                    string path = Path.Combine(saveFolder, filename);
                    await File.WriteAllBytesAsync(path, bytes);

                    AppendLog("单次截图成功。");
                    await core.CallDevToolsProtocolMethodAsync("Emulation.clearDeviceMetricsOverride", "{}");
                    SetProgress(100, "已保存 (单次截图)");
                    return path;
                }
                catch
                {
                    await core.CallDevToolsProtocolMethodAsync("Emulation.clearDeviceMetricsOverride", "{}");
                    AppendLog("单次截图失败，使用滚动拼接。");
                }
            }

            // 6️⃣ 使用滚动拼接
            AppendLog("内容过大或单次截图失败，使用滚动拼接截图。");
            return await CaptureByScrollAsync(core, contentWidth, contentHeight, viewportWidth, viewportHeight, dpr, saveFolder, token);
        }

        #endregion
    }
}
